﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Asol.Reporting.Support;
using Asol.Reporting.Support.Parser;

namespace Asol.Reporting.Editor.SqlCommands
{
    #region Top nnn [percent]
    /// <summary>
    /// SqlPart: Top nnn [percent]
    /// </summary>
    public class SqlPartTop : SqlPart
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public SqlPartTop()
        {
            this.TopExpression = new SqlPartExpression() { SimpleExpression = true };
        }
        /// <summary>
        /// Výraz určující počet TOP
        /// </summary>
        public SqlPartExpression TopExpression { get; private set; }
        /// <summary>
        /// Příznak suffixu PERCENT
        /// </summary>
        public bool TopPercent { get; private set; }
        /// <summary>
        /// Příznak suffixu WITH TIES
        /// </summary>
        public bool WithTies { get; private set; }
        /// <summary>
        /// Klíčové slovo
        /// </summary>
        protected override string Keywords { get { return "TOP"; } }
        /// <summary>
        /// Načítá svoje položky ze streamu
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="keyword"></param>
        protected override void Load(ParserStream stream, string keyword)
        {
            base.Load(stream, keyword);
            this.TopExpression.LoadFrom(stream);
            this.LoadPercent(stream);
            this.LoadWithTies(stream);
        }
        /// <summary>
        /// Zajistí načtení příznaku "PERCENT"
        /// </summary>
        /// <param name="stream"></param>
        protected void LoadPercent(ParserStream stream)
        {
            string keyword;
            this.TopPercent = stream.StartsWithKeyword("PERC, PERCENT", out keyword);
            if (this.TopPercent)
                stream.SkipKeyword(keyword);
        }
        /// <summary>
        /// Zajistí načtení příznaku "WITH TIES"
        /// </summary>
        /// <param name="stream"></param>
        protected void LoadWithTies(ParserStream stream)
        {
            string keyword;
            this.WithTies = stream.StartsWithKeyword("WITH TIES", out keyword);
            if (this.WithTies)
                stream.SkipKeyword(keyword);
        }
    }
    #endregion
    #region Field list with aliases
    /// <summary>
    /// SqlPart: Field list with aliases
    /// </summary>
    public class SqlPartFields : SqlPartExpressions
    {
        public SqlPartFields() { }
        protected override string Keywords { get { return "?"; } }
        protected override bool AliasEnabled { get { return true; } }
        protected override bool SortOrderEnabled { get { return false; } }
    }
    #endregion
    #region Source one (table/view/... from, join, on, hints)
    /// <summary>
    /// SqlPart: Source
    /// </summary>
    public class SqlPartSource : SqlPart
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public SqlPartSource()
        {
            this.JoinType = SqlJoinType.None;
            this.SourceExpression = new SqlPartExpression();
            this.SourceExpression.AliasEnabled = true;
            this.SourceHint = new SqlPartHints();
            this.JoinExpression = new SqlPartExpressionJoin();
        }
        /// <summary>
        /// Může za tabulkou být jeho alias?
        /// </summary>
        public bool AliasEnabled { get; set; }
        /// <summary>
        /// Pořadové číslo zdroje v SqlPartSources
        /// </summary>
        public int SourceIndex { get; set; }
        /// <summary>
        /// Typ zapojení zdroje do dotazu
        /// </summary>
        public SqlJoinType JoinType { get { return this._JoinType; } private set { this._SetJoinType(value);  } }
        /// <summary>
        /// Obsahuje text hintu: LEFT OUTER hint JOIN
        /// </summary>
        public ParserSegmentValue JoinHintValue { get { return this._JoinHintValue; } private set { this._JoinHintValue = value; } }
        /// <summary>
        /// Příznak, zda obsahuje výraz "ON (expression)"
        /// </summary>
        protected bool ContainOnPart { get; private set; }
        /// <summary>
        /// Výraz představující zdroj. Typicky je to jméno tabulky, ale může to být i subselect.
        /// </summary>
        public SqlPartExpression SourceExpression { get; private set; }
        /// <summary>
        /// Tabulkové hinty : with (readuncommitted)
        /// </summary>
        public SqlPartHints SourceHint { get; private set; }
        /// <summary>
        /// Alias zdroje
        /// </summary>
        public string Alias { get; private set; }
        /// <summary>
        /// Výraz pro JOIN nalezený za ON
        /// </summary>
        public SqlPartExpressionJoin JoinExpression { get; private set; }
        /// <summary>
        /// Klíčová slova uvozující Source
        /// </summary>
        protected override string Keywords { get { return GetKeywords(this.SourceIndex); } }
        /// <summary>
        /// Načte obsah ze streamu
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="keyword"></param>
        protected override void Load(ParserStream stream, string keyword)
        {
            List<ParserSegmentValue> values;
            base.Load(stream, keyword, out values);
            this._DetectJoinHint(keyword, values);
            this.JoinType = ConvertJoinType(keyword);
            this.SourceExpression.LoadDirect(stream);
            this.SourceHint.LoadFrom(stream);
            if (this.ContainOnPart)
                this.JoinExpression.LoadFrom(stream);
        }
        /// <summary>
        /// Detekuje hint z fráze "LEFT OUTER {hint} JOIN" a uloží jej do sebe. Hintem je žádné nebo jedno slovo.
        /// </summary>
        /// <param name="keyword"></param>
        /// <param name="values"></param>
        private void _DetectJoinHint(string keyword, List<ParserSegmentValue> values)
        {
            this._JoinHintValue = null;
            if (values == null) return;

            KeywordPhrase phrase = new KeywordPhrase(keyword);
            for (int w = 0; w < phrase.WordCount; w++)
            {
                if (w >= values.Count) break;
                if (phrase.Words[w] == "?")
                {
                    this._JoinHintValue = values[w];
                    break;
                }
            }
        }
        /// <summary>
        /// Uloží hodnotu do JoinType a reaguje na ni (nastaví odpovídající vlastnosti do sebe a do svých objektů).
        /// </summary>
        /// <param name="value"></param>
        private void _SetJoinType(SqlJoinType value)
        {
            this._JoinType = value;
            if (value == SqlJoinType.From || value == SqlJoinType.Next)
            {
                this.ContainOnPart = false;
            }
            else if ((value & SqlJoinType.Join) > 0) 
            {
                this.ContainOnPart = true;
            }
        }
        private SqlJoinType _JoinType;
        private ParserSegmentValue _JoinHintValue;
        #region Konverze stringu na typ FROM/JOIN
        /// <summary>
        /// Konvertuje klíčová slova FROM/JOIN na odpovídající datový typ, a naopak
        /// </summary>
        /// <param name="keyWord">Klíčové slovo/sousloví (například "LEFT OUTER ? JOIN")</param>
        /// <returns>Odpovídající typ FROM/JOIN</returns>
        public static SqlJoinType ConvertJoinType(string keyWord)
        {
            if (string.IsNullOrEmpty(keyWord))
                return SqlJoinType.None;

            SqlJoinType result = SqlJoinType.None;
            string text = " " + keyWord.ToUpper().Trim() + " ";
            if (text == " , ")
                return SqlJoinType.Next;
            if (text == " FROM ")
                return SqlJoinType.From;
            if (text.Contains(" JOIN "))
                result |= SqlJoinType.Join;
            if (text.Contains(" INNER "))
                result |= SqlJoinType.Inner;
            if (text.Contains(" OUTER "))
                result |= SqlJoinType.Outer;
            if (text.Contains(" LEFT "))
                result |= SqlJoinType.Left;
            if (text.Contains(" RIGHT "))
                result |= SqlJoinType.Right;
            if (text.Contains(" FULL "))
                result |= SqlJoinType.Full;
            if (text.Contains(" ? "))
                result |= SqlJoinType.Hint;

            return result;
        }
        /// <summary>
        /// Konvertuje klíčová slova FROM/JOIN na odpovídající datový typ, a naopak
        /// </summary>
        /// <param name="join">Typ FROM/JOIN</param>
        /// <returns>Odpovídající klíčové slovo/sousloví (například "LEFT OUTER ? JOIN")</returns>
        public static string ConvertJoinType(SqlJoinType join)
        {
            if (join == SqlJoinType.None) return "";              // Nic
            if ((join & SqlJoinType.From) > 0) return "FROM";     // FROM se s ničím nekombinuje
            if ((join & SqlJoinType.Next) > 0) return ",";        // NEXT je čárka (v selectu je mezi table1 a table2 : "SELECT * FROM table1 t1, table2 t2 WHERE t1.key = t2.key")
            if ((join & SqlJoinType.Join) == 0) return "";        // Neobsahuje-li JOIN => nic.

            // Korektní pořadí a vzájemné vztahy: [ { INNER | { { LEFT | RIGHT | FULL } [ OUTER ] } } [ <join_hint> ] ] JOIN

            string result = "";
            if ((join & SqlJoinType.Left) > 0) result += " LEFT";
            else if ((join & SqlJoinType.Right) > 0) result += " RIGHT";
            else if ((join & SqlJoinType.Full) > 0) result += " FULL";
            else if ((join & SqlJoinType.Inner) > 0) result += " INNER";

            if ((join & SqlJoinType.Inner) == 0 && result.Length > 0 && (join & SqlJoinType.Outer) > 0) result += " OUTER";

            if (result.Length > 0 && (join & SqlJoinType.Hint) > 0) result += " ?";

            result += " JOIN";
            return result.Trim();
        }
        /// <summary>
        /// Metoda vrací platnou sadu klíčových slov, jimiž může začínat tato sekvence (nevrací nepřípustné kombinace).
        /// </summary>
        /// <returns></returns>
        protected static string GetKeywords(int sourceIndex)
        {
            // Korektní pořadí a vzájemné vztahy: [ { INNER | { { LEFT | RIGHT | FULL } [ OUTER ] } } [ <join_hint> ] ] JOIN
            string separator = ParserStream.KEYWORDS_SEPARATOR;
            if (sourceIndex == 0)
                return
                    ConvertJoinType(SqlJoinType.From);
            else
                return 
                    ConvertJoinType(SqlJoinType.Next) + separator +
                    ConvertJoinType(SqlJoinType.InnerJoin) + separator +
                    ConvertJoinType(SqlJoinType.InnerHintJoin) + separator +
                    ConvertJoinType(SqlJoinType.LeftJoin) + separator +
                    ConvertJoinType(SqlJoinType.LeftHintJoin) + separator +
                    ConvertJoinType(SqlJoinType.LeftOuterJoin) + separator +
                    ConvertJoinType(SqlJoinType.LeftOuterHintJoin) + separator +
                    ConvertJoinType(SqlJoinType.RightJoin) + separator +
                    ConvertJoinType(SqlJoinType.RightHintJoin) + separator +
                    ConvertJoinType(SqlJoinType.RightOuterJoin) + separator +
                    ConvertJoinType(SqlJoinType.RightOuterHintJoin) + separator +
                    ConvertJoinType(SqlJoinType.FullJoin) + separator +
                    ConvertJoinType(SqlJoinType.FullOuterJoin) + separator +
                    ConvertJoinType(SqlJoinType.FullHintJoin) + separator +
                    ConvertJoinType(SqlJoinType.FullOuterHintJoin) + separator +
                    ConvertJoinType(SqlJoinType.Join);
        }
        /// <summary>
        /// Statická cache klíčového slova
        /// </summary>
        private static string _Keywords = null;
        #endregion
    }
    #endregion
    #region Sources list (FROM, JOIN [nn])
    /// <summary>
    /// SqlPartSources : soustava zdrojů
    /// </summary>
    public class SqlPartSources : SqlPart
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public SqlPartSources()
        {
            this.SourceList = new List<SqlPartSource>();
        }
        /// <summary>
        /// Seznam zdrojů = tabulky, view, subselecty, procedury...
        /// </summary>
        public List<SqlPartSource> SourceList { get; private set; }
        /// <summary>
        /// Klíčová slova uvozující Sources = FROM
        /// </summary>
        protected override string Keywords { get { return "FROM"; } }
        /// <summary>
        /// Načte svoje data.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="keyWord"></param>
        protected override void Load(ParserStream stream, string keyWord)
        {
            // stream je ve stavu, kdy začíná výchozím slovem (FROM).
            // To slovo využijeme k tomu, aby si objekt třídy SqlPartSource sám určil svoje zatřídění do soustavy zdrojů.
            // Nebudeme tedy dávat příkaz { base.Load(stream, keyWord); }, tím by se slovo FROM ze streamu odebralo.
            while (true)
            {
                SqlPartSource source = new SqlPartSource();
                source.SourceIndex = this.SourceList.Count;
                source.LoadFrom(stream);
                if (!source.IsUsed) break;
                this.SourceList.Add(source);
            }
        }
    }
    #endregion
    #region Hints
    public class SqlPartHints : SqlPart
    {
        public SqlPartHints()
        { }
        public ParserSegmentValue HintValue { get; private set; }
        protected override string Keywords { get { return "WITH"; } }
        protected override void Load(ParserStream stream, string keyword)
        {
            base.Load(stream, keyword);
            ParserSegmentValue value = stream.PeekValue();
            this.IsUsed = (value.HasInnerSegment && value.InnerSegment.SegmentName == ParserDefaultSetting.SQL_PARENTHESIS);
            if (this.IsUsed)
                this.HintValue = stream.ReadValue();
        }
    }
    #endregion
    #region Where
    public class SqlPartWhere : SqlPart
    {
        public SqlPartWhere()
        {
            this.Expression = new SqlPartExpression();
        }
        public SqlPartExpression Expression { get; private set; }
        protected override string Keywords { get { return "WHERE"; } }
        protected override void Load(ParserStream stream, string keyword)
        {
            base.Load(stream, keyword);
            this.Expression.LoadFrom(stream);
        }
    }
    #endregion
    #region Order by columns
    /// <summary>
    /// SqlPart: Order by columns
    /// </summary>
    public class SqlPartOrder : SqlPartExpressions
    {
        public SqlPartOrder() { }
        protected override string Keywords { get { return "ORDER BY"; } }
        protected override bool AliasEnabled { get { return false; } }
        protected override bool SortOrderEnabled { get { return true; } }
    }
    #endregion
    #region Group by columns
    /// <summary>
    /// SqlPart: Group by columns
    /// </summary>
    public class SqlPartGroup : SqlPartExpressions
    {
        public SqlPartGroup() { }
        protected override string Keywords { get { return "GROUP BY"; } }
        protected override bool AliasEnabled { get { return false; } }
        protected override bool SortOrderEnabled { get { return false; } }
    }
    #endregion
    #region Expressions list
    /// <summary>
    /// SqlPart: Expressions list
    /// </summary>
    public class SqlPartExpressions : SqlPart
    {
        public SqlPartExpressions()
        {
            this.Expressions = new List<SqlPartExpression>();
        }
        public List<SqlPartExpression> Expressions { get; private set; }
        protected override string Keywords { get { return "?"; } }
        protected virtual bool AliasEnabled { get { return false; } }
        protected virtual bool SortOrderEnabled { get { return false; } }
        protected override void Load(ParserStream stream, string keyWord)
        {
            base.Load(stream, keyWord);

            while (!stream.EndOfStream)
            {
                SqlPartExpression expression = new SqlPartExpression();
                expression.AliasEnabled = this.AliasEnabled;
                expression.SortOrderEnabled = this.SortOrderEnabled;
                expression.LoadDirect(stream);
                this.Expressions.Add(expression);
                ParserSegmentValue value = stream.PeekValue();
                if (value == null) break;
                bool next = (value.ValueType == ParserSegmentValueType.Delimiter && value.Content == ",");
                if (!next) break;
                stream.SkipValue();                   // Na aktuální pozici je Delimiter ","
            }
        }
    }
    #endregion
    #region Expression
    /// <summary>
    /// SqlPart: Expression one
    /// </summary>
    public class SqlPartExpression : SqlPart
    {
        public SqlPartExpression()
        {
            this.ExpressionValues = new List<ParserSegmentValue>();
        }
        /// <summary>
        /// Povoluje se výraz s pouze jednou hodnotou (Value). Ta může být vnořený segment (tedy v závorce).
        /// Tzn. složité výrazy pouze v závorce.
        /// Například: povoluje se výraz "100", 
        /// ale v případě streamu "100 * 200..." se vrátí jen expression "100" a stream bude pokračovat "* 200...".
        /// </summary>
        public bool SimpleExpression { get; set; }
        /// <summary>
        /// Může za výrazem být jeho alias?
        /// </summary>
        public bool AliasEnabled { get; set; }
        /// <summary>
        /// Může za výrazem být příznak ASC / DESC ?
        /// </summary>
        public bool SortOrderEnabled { get; set; }
        /// <summary>
        /// Seznam prvků, které tvoří vlastní výraz.
        /// Pokud tento objekt registruje i alias a order (ASC/DESC), pak tato slova nejsou součástí ExpressionValues.
        /// Kompletní soupis prvků načtených v tomto objektu (tedy včetně Aliasu a Sort) je v property Values.
        /// </summary>
        public List<ParserSegmentValue> ExpressionValues { get; private set; }
        /// <summary>
        /// Textové vyjádření výrazu (ExpressionValues převedený na string)
        /// </summary>
        public string Expression { get { return ParserStream.ToText(this.ExpressionValues); } }
        /// <summary>
        /// Zjištěný alias výrazu, pokud je povolen (this.AliasEnabled)
        /// </summary>
        public string Alias { get; private set; }
        /// <summary>
        /// Směr třídění pro tento výraz, pokud je povolen (this.OrderSortEnabled)
        /// </summary>
        public SortOrderType SortOrder { get; private set; }
        /// <summary>
        /// Klíčová slova této části
        /// </summary>
        protected override string Keywords { get { return "?"; } }
        /// <summary>
        /// Načte obsah této části
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="keyWord"></param>
        protected override void Load(ParserStream stream, string keyWord)
        {
            // if (keyWord != null && keyWord != "?")
            base.Load(stream, keyWord);

            ValueFlowState state = ValueFlowState.None;
            while (!stream.EndOfStream)
            {
                ParserSegmentValue value = stream.PeekValue();
                if (value == null) break;
                state = DetectHandlingOfValue(value, state);
                if (state == ValueFlowState.Outside) break;

                switch (state)
                {
                    case ValueFlowState.None:
                        stream.SkipValue();
                        break;
                    case ValueFlowState.Text:
                    case ValueFlowState.Operator:
                    case ValueFlowState.Delimiter:
                        this.ExpressionValues.Add(value);
                        stream.SkipValue();
                        break;
                    case ValueFlowState.As:
                        stream.SkipValue();
                        break;
                    case ValueFlowState.Alias:
                        this.Alias = value.Content;
                        stream.SkipValue();
                        break;
                    case ValueFlowState.OrderSort:
                        this.SortOrder = ConvertSortOrder(value.Content);
                        stream.SkipValue();
                        break;
                    case ValueFlowState.Case:
                        state = this.LoadCase(stream, state);
                        break;
                }

                if (this.SimpleExpression && this.ExpressionValues.Count > 0)
                    break;
            }
        }
        /// <summary>
        /// Detekuje další tok textu v závislosti na stávajícím stavu a na aktuálním prvku
        /// </summary>
        /// <param name="value"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        private ValueFlowState DetectHandlingOfValue(ParserSegmentValue value, ValueFlowState state)
        {
            string content;
            switch (value.ValueType)
            {
                case ParserSegmentValueType.Delimiter:
                    // string[] delimiters = new string[] { ",", "+", "-", "*", "/", "<", "=", ">", "<=", ">=", "<>", "%" };
                    if (value.Content == ",")
                        return ValueFlowState.Outside;
                    return ValueFlowState.Delimiter;

                case ParserSegmentValueType.InnerSegment:
                    return ValueFlowState.Text;

                case ParserSegmentValueType.Text:
                    content = value.Content.ToLower();
                    switch (state)
                    {   // state obsahuje minulý stav detekovaný touto metodou.
                        case ValueFlowState.None:
                            if (IsExpressionOperators(value))
                                return ValueFlowState.Operator;         // AND, IS, NULL, ...

                            if (content == "case")
                                return ValueFlowState.Case;

                            if (value.ValueName == Parser.VALUENAME_KEYWORD)
                                return ValueFlowState.Outside;          // FROM, WHERE, ...

                            return ValueFlowState.Text;

                        case ValueFlowState.Text:
                            // Text následující za předchozím textem bez oddělovače (tj. bez operátoru) může být alias, nebo klíčové slovo, nebo chyba => nic:
                            if (IsExpressionOperators(value))
                                return ValueFlowState.Operator;         // AND, IS, NULL, ...

                            if (this.AliasEnabled && content == "as")
                                return ValueFlowState.As;

                            if (this.SortOrderEnabled && (content == "asc" || content == "desc"))
                                return ValueFlowState.OrderSort;

                            if (content == "case")                      // poznámka: klíčová slova WHEN, THEN a END se neošetřují explicitně, ale vyběhnou jako normální keywordy => Outside. Ošetřuje je metoda Load() třídy SqlPartExpressionCaseWhen.
                                return ValueFlowState.Case;             // Zde uvnitř Expression potřebujeme pouze detekovat začátek větve CASE.

                            if (value.ValueName == Parser.VALUENAME_KEYWORD)
                                return ValueFlowState.Outside;          // FROM, WHERE, WHEN, THEN, ELSE, END ...

                            if (this.AliasEnabled)
                                return ValueFlowState.Alias;

                            return ValueFlowState.Outside;

                        case ValueFlowState.Case:
                        case ValueFlowState.When:
                        case ValueFlowState.Then:
                            // sekvenci case - when - then ošetřujeme jinde.
                            return state;

                        case ValueFlowState.As:
                            return ValueFlowState.Alias;                // Text za textem:   "... as ALIAS" je ALIAS

                        case ValueFlowState.OrderSort:
                        case ValueFlowState.Alias:
                            return ValueFlowState.Outside;              // Text za aliasem i za ASC/DESC je mimo

                        case ValueFlowState.Delimiter:                  // Text za oddělovačem (,*/+) je text:   "... + TEXT"
                        case ValueFlowState.Operator:                   // Text za operátorem (and, is, null, atd) je text:   "... + TEXT"
                            if (IsExpressionOperators(value))
                                return ValueFlowState.Operator;         // AND, IS, NULL, ...

                            if (this.AliasEnabled && content == "as")
                                return ValueFlowState.As;

                            if (this.SortOrderEnabled && (content == "asc" || content == "desc"))
                                return ValueFlowState.OrderSort;

                            if (content == "case")                      // poznámka: klíčová slova WHEN, THEN a END se neošetřují explicitně, ale vyběhnou jako normální keywordy => Outside. Ošetřuje je metoda Load() třídy SqlPartExpressionCaseWhen.
                                return ValueFlowState.Case;             // Zde uvnitř Expression potřebujeme pouze detekovat začátek větve CASE.

                            if (value.ValueName == Parser.VALUENAME_KEYWORD)
                                return ValueFlowState.Outside;          // FROM, WHERE, WHEN, THEN, ELSE, END ...

                            return ValueFlowState.Text;
                    }
                    break;
            }
            return ValueFlowState.Outside;
        }
        /// <summary>
        /// Vrací true, pokud value je text (některý z): and, or, is, not, null, in, list, like
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private bool IsExpressionOperators(ParserSegmentValue value)
        {
            if (value == null || value.ValueType != ParserSegmentValueType.Text) return false;
            string content = value.Content.ToLower();
            return (content == "and" ||
                    content == "or" ||
                    content == "is" ||
                    content == "not" ||
                    content == "null" ||
                    content == "in" ||
                    content == "list" ||
                    content == "like");
        }
        /// <summary>
        /// Vrací druh třídění podle slova ASC / DESC
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        private SortOrderType ConvertSortOrder(string content)
        {
            string text = content.ToLower();
            if (text == "asc") return SortOrderType.Asc;
            if (text == "desc") return SortOrderType.Desc;
            return SortOrderType.None;
        }
        /// <summary>
        /// Vrací druh třídění podle slova ASC / DESC
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        private string ConvertSortOrder(SortOrderType sortOrder)
        {
            switch (sortOrder)
            {
                case SortOrderType.Asc: return "ASC ";
                case SortOrderType.Desc: return "DESC ";
            }
            return "";
        }
        /// <summary>
        /// Zajistí načtení výrazu CASE - WHEN - THEN - END jako jeden vnořený segment.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        private ValueFlowState LoadCase(ParserStream stream, ValueFlowState state)
        {
            ParserSegmentValue valueCase = stream.PeekValue();
            SqlPartExpressionCase caseWhen = new SqlPartExpressionCase(valueCase);
            caseWhen.Load(stream);
            this.ExpressionValues.Add(caseWhen);
            return ValueFlowState.Text;
        }
    }
    /// <summary>
    /// Průběžný stav detektoru Expression
    /// </summary>
    public enum ValueFlowState
    {
        None,
        Text,
        Operator,
        Delimiter,
        As,
        Alias,
        OrderSort,
        Case,
        When,
        Then,
        Else,
        Outside
    }
    /// <summary>
    /// Směr třídění
    /// </summary>
    public enum SortOrderType
    {
        None,
        Asc,
        Desc
    }
    #endregion
    #region ExpressionJoin
    /// <summary>
    /// Výraz ON u joinované tabulky. Liší se keywordem.
    /// </summary>
    public class SqlPartExpressionJoin : SqlPartExpression
    {
        public SqlPartExpressionJoin()
        {}
        protected override string Keywords { get { return "ON"; } }
    }
	#endregion
    #region SqlPartExpressionCase, SqlPartExpressionWhenThen
    /// <summary>
    /// SqlPartExpressionCase : potomek ParserSegmentValue (to kvůli zatřídění do List(Value), 
    /// obsahující všechny Values z fráze CASE - WHEN - THEN - END, 
    /// obsahující navíc logickou skladbu tohoto výrazu (CASE - (WHEN:THEN) - ELSE).
    /// </summary>
    public class SqlPartExpressionCase : ParserSegmentValue
    {
        #region Konstrukce
        public SqlPartExpressionCase(ParserSegmentValue valueCase)
            : base(valueCase.Parent, ParserSegmentValueType.InnerSegment)
        {
            this.ValueName = SqlCommand.EXPR_CASE;
            this.InnerSegment = new ParserSegment(this, valueCase.Parent);
            this.CaseExpression = new SqlPartExpression();
            this.WhenThenList = new List<SqlPartExpressionWhenThen>();
        }
        public override string  ToString()
        {
 	         return this.Text;
        }
		#endregion
        #region Public property
        /// <summary>
        /// Výraz za CASE (tj. zdroj hodnoty, která se detekuje v WHEN)
        /// </summary>
        public SqlPartExpression CaseExpression { get; private set; }
        /// <summary>
        /// Sada párů podmínek WHEN - THEN.
        /// Poslední z nich může mít podmínku WHEN nepoužitou, pak jde o větev ELSE.
        /// </summary>
        public List<SqlPartExpressionWhenThen> WhenThenList { get; private set; }
        /// <summary>
        /// Text chyby nalezené v této části
        /// </summary>
        public string Error { get; protected set; }
        /// <summary>
        /// Položka segmentu, která nebyla očekávaná a na níž došlo k chybě (this.Error)
        /// </summary>
        public ParserSegmentValue ErrorAtValue { get; protected set; }
		#endregion
        #region Načítání výrazu
        /// <summary>
        /// Načte do sebe výraz CASE - WHEN-THEN - ELSE - END
        /// </summary>
        /// <param name="stream"></param>
        public void Load(ParserStream stream)
        {
            ValueFlowState state = ValueFlowState.None;
            SqlPartExpressionWhenThen whenThen = null;
            while (!stream.EndOfStream)
            {
                ParserSegmentValue value = stream.PeekValue();
                if (value.ValueType == ParserSegmentValueType.Text && value.ValueName == Parser.VALUENAME_KEYWORD)
                {   // Očekávám pouze holé texty, klíčová slova:
                    string text = value.Text.ToLower();
                    switch (text)
                    {
                        case "case":
                            if (state == ValueFlowState.None)
                            {
                                this.AddValueToInnerSegment(value, stream);
                                this.CaseExpression.LoadDirect(stream);
                                this.AddValuesToInnerSegment(this.CaseExpression.ExpressionValues);
                                state = ValueFlowState.Case;
                            }
                            break;
                        case "when":
                            if (state == ValueFlowState.Case || state == ValueFlowState.Then)
                            {
                                this.AddValueToInnerSegment(value, stream);
                                whenThen = new SqlPartExpressionWhenThen();
                                whenThen.ExpressionWhen.LoadDirect(stream);
                                this.AddValuesToInnerSegment(whenThen.ExpressionWhen.ExpressionValues);
                                state = ValueFlowState.When;
                            }
                            break;
                        case "then":
                            if (state == ValueFlowState.When)
                            {
                                this.AddValueToInnerSegment(value, stream);
                                whenThen.ExpressionThen.LoadDirect(stream);
                                this.AddValuesToInnerSegment(whenThen.ExpressionThen.ExpressionValues);
                                this.WhenThenList.Add(whenThen);
                                whenThen = null;
                                state = ValueFlowState.Then;
                            }
                            break;
                        case "else":
                            if (state == ValueFlowState.Then)
                            {
                                this.AddValueToInnerSegment(value, stream);
                                whenThen = new SqlPartExpressionWhenThen();
                                whenThen.ExpressionThen.LoadDirect(stream);
                                this.AddValuesToInnerSegment(whenThen.ExpressionThen.ExpressionValues);
                                this.WhenThenList.Add(whenThen);
                                whenThen = null;
                                state = ValueFlowState.Else;
                            }
                            break;
                        case "end":
                            this.AddValueToInnerSegment(value, stream);
                            state = ValueFlowState.Outside;
                            break;
                    }
                }
                else
                {   // Není to klíčové slovo = došlo k nějaké chybě, poznamenáme si to a opustíme parsování výrazu:
                    this.Error = "Unexpected value «" + value.Text + "», in CASE expression «" + this.Text + "».";
                    this.ErrorAtValue = value;
                    state = ValueFlowState.Outside;
                }
                if (state == ValueFlowState.Outside)
                    break;
            }
        }
        protected void AddValueToInnerSegment(ParserSegmentValue value, ParserStream stream)
        {
            if (stream != null)
                stream.SkipValue();
            if (this.InnerSegment.ValueList.Count > 0)
                this.InnerSegment.AddBlank(" ");
            this.InnerSegment.AddValue(value);
        }
        protected void AddValuesToInnerSegment(IEnumerable<ParserSegmentValue> values)
        {
            if (this.InnerSegment.ValueList.Count > 0)
                this.InnerSegment.AddBlank(" ");
            this.InnerSegment.AddValues(values);
        }
		#endregion
    }
    /// <summary>
    /// Pár výrazů WHEN - THEN
    /// </summary>
    public class SqlPartExpressionWhenThen
    {
        public SqlPartExpressionWhenThen()
        {
            this.ExpressionWhen = new SqlPartExpression();
            this.ExpressionThen = new SqlPartExpression();
        }
        /// <summary>
        /// Podmínka (WHEN)
        /// </summary>
        public SqlPartExpression ExpressionWhen { get; private set; }
        /// <summary>
        /// Hodnota (THEN)
        /// </summary>
        public SqlPartExpression ExpressionThen { get; private set; }
    }
    #endregion
    #region enums
    [Flags]
    public enum SqlJoinType
    {
        /// <summary>none</summary>
        None = 0,
        /// <summary>FROM</summary>
        From = 1,
        /// <summary>další v řadě FROM (za čárkou, bez JOINu)</summary>
        Next = From << 1,
        /// <summary>JOIN</summary>
        Join = Next << 1,
        /// <summary>INNER</summary>
        Inner = Join << 1,
        /// <summary>OUTER</summary>
        Outer = Inner << 1,
        /// <summary>LEFT</summary>
        Left = Outer << 1,
        /// <summary>RIGHT</summary>
        Right = Left << 1,
        /// <summary>FULL</summary>
        Full = Right << 1,
        /// <summary>hint v JOINu</summary>
        Hint = Full << 1,
        /// <summary>INNER JOIN</summary>
        InnerJoin = Inner | Join,
        /// <summary>INNER hint JOIN</summary>
        InnerHintJoin = Inner | Hint | Join,
        /// <summary>LEFT JOIN</summary>
        LeftJoin = Left | Join,
        /// <summary>RIGHT JOIN</summary>
        RightJoin = Right | Join,
        /// <summary>FULL JOIN</summary>
        FullJoin = Full | Join,
        /// <summary>LEFT hint JOIN</summary>
        LeftHintJoin = Left | Hint | Join,
        /// <summary>RIGHT hint JOIN</summary>
        RightHintJoin = Right | Hint | Join,
        /// <summary>FULL hint JOIN</summary>
        FullHintJoin = Full | Hint | Join,
        /// <summary>LEFT OUTER JOIN</summary>
        LeftOuterJoin = Left | Outer | Join,
        /// <summary>RIGHT OUTER JOIN</summary>
        RightOuterJoin = Right | Outer | Join,
        /// <summary>FULL OUTER JOIN</summary>
        FullOuterJoin = Full | Outer | Join,
        /// <summary>LEFT OUTER hint JOIN</summary>
        LeftOuterHintJoin = Left | Outer | Hint | Join,
        /// <summary>RIGHT OUTER hint JOIN</summary>
        RightOuterHintJoin = Right | Outer | Hint | Join,
        /// <summary>FULL OUTER hint JOIN</summary>
        FullOuterHintJoin = Full | Outer | Hint | Join
    }
    #endregion
}
